package main

import (
	"sync"
	"fmt"
	"time"
)

type hash_record struct {
	value string
	time_written int64
}

// This is the core hashtable for our system
var hashtable []map[string]hash_record
var partitionMutex []sync.Mutex;

func initHashTables(n int) {
	hashtable = make([]map[string]hash_record,n+2,100)	
	for i:=1; i< n+2; i++ {
		hashtable[i]=make(map[string]hash_record)
	}
	partitionMutex = make([]sync.Mutex,n+2,100)
}

//Determining partition number and sending the read there
func readTable(key string) string{
	hashedkey := hash(key)
	partitionNumber := findPartition(hashedkey)
	return readPartition(partitionNumber, key)
}

//Determining partition number and sending the write there
func writeTable(key string, value string) string {
	hashedkey := hash(key)
	partitionNumber := findPartition(hashedkey)
	return writePartition(partitionNumber, key, value)
}

// Reading the partition and returning a value. Default is NIL
func readPartition(partitionNumber int, key string) string {
	partitionMutex[partitionNumber].Lock();
	//fmt.Println("Accessing partition",partitionNumber," at this server for key",key)
	//printAllLogs();
	//fmt.Println("Current hashtable for this partition:")
	//printPartition(partitionNumber)	
	if readValue, ok := hashtable[partitionNumber][key]; ok {
		partitionMutex[partitionNumber].Unlock();
	       	return readValue.value
    	}
	
	partitionMutex[partitionNumber].Unlock();
	return "NIL"
}

// Writing a new value into the partition and returning the old value
func writePartition(partitionNumber int,key string, value string) string {
	//fmt.Println("Inside writePartition, waiting for lock")
	partitionMutex[partitionNumber].Lock();
	//fmt.Println("Inside write partition")
	//fmt.Println("Current hashtable for this partition:")
	//printPartition(partitionNumber)	
	oldValue := "NIL"
	//fmt.Println("Log_append done")
	if readValue, ok := hashtable[partitionNumber][key]; ok {   
		oldValue=readValue.value
    	}
	//fmt.Println("After reading value")
	var hrecord hash_record
	hrecord.time_written = time.Nanoseconds()
	hrecord.value = value
	hashtable[partitionNumber][key]=hrecord
	log_append(partitionNumber,key,value);
	//fmt.Println("Still inside write, going to unlock")
	partitionMutex[partitionNumber].Unlock();	
	return oldValue
}

// Writing a new value into the partition and returning the old value
// This time also specifiying the time at which the write was carried out
// This is used when writing a value from a log into the hashtable
func writePartitionWithTime(partitionNumber int,key string, value string, time_written int64) string {
	//fmt.Println("Writing partitions for logs, waiting for lock",key,value,time_written)
	partitionMutex[partitionNumber].Lock();
	//fmt.Println("Current hashtable for this partition:")
	//printPartition(partitionNumber)	
	oldValue := "NIL"
	var oldTime int64
	oldTime = 0
	if readValue, ok := hashtable[partitionNumber][key]; ok {   
		oldValue=readValue.value
		oldTime = readValue.time_written
    	}
	var hrecord hash_record
	hrecord.time_written = time_written
	hrecord.value = value
	
	// Check if it is ok to overwrite value
	if oldTime >= time_written {
		//Other update got there first
		//fmt.Println("Discarding old update ",value,"to",key);
		partitionMutex[partitionNumber].Unlock();
		return "DISCARDED"
	}

	//fmt.Println("WritePartwithTime: Merging value",key,"=",value,"at time",time_written," into hash table")

	hashtable[partitionNumber][key]=hrecord
	log_append(partitionNumber,key,value);
	partitionMutex[partitionNumber].Unlock();	
	return oldValue
}

// Hash the string onto some value. The partitioning is done based on this hashed value.
func hash(key string) int {
     	hashedkey := 0
        for i := 0; i < len(key); i++ {
	        hashedkey += int(key[i])
	}
	return hashedkey
}

func printPartition(partitionNumber int) {
	fmt.Println("Printing hashtable for parition",partitionNumber)
	for key,hvalue := range hashtable[partitionNumber] {
		fmt.Println(key,hvalue.value,hvalue.time_written)
	}
}
